#include <stdio.h>

int main()
{
#if 0
    // extern 做一个外部变量说明.
    // 在引用该全局变量的文件中的头部,做一个"外部变量说明",说明这里出现的变量是一个已经在其他文件中定义过的外部变量,本文件不必引用.
    // 在定义全局变量时前面加static,说明该全局变量智能在本文件中使用.
    // 跨文件调用,我们根据根据函数能否在本文件中其他函数调用,定义内部函数的时候,在函数定义最前面加一个static
    // 内部函数又称为静态函数,只能够局限于该文件
    // 外部函数:如果一个函数不用static,他就是外部函数,当然你可以在定义该函数时,前面加一个extern,但默认就是extern.

    // static 关键字
    // 1.函数内部定义一个static变量,该变量会保存在静态存储区
    //   编译的时候初始化0,并且下次调用该函数时,会保持上次离开该函数的值
    // 2.定义static全局变量,该全局变量只能够在本文件中使用.
    // 3.定义static函数,只能够在本文件使用
#endif
#if 0
    // 1.不带参数的宏定义
    // -->一个项目通过编译,链接生成一个可执行文件
    // -->每个源文件都会单独编译,生成.o文件,系统把这些.o进行链接生成一个可执行文件.
    // 预处理,编译(词法),汇编生成.o
    // 预处理:一般c语言提供三种1.宏定义 2.文件包含 3,条件编译
    // 都是#号开头

    // 1.不带参数的宏, 一个指定的标志符,代表一个字符串
    // #define 标志符/宏名 字符串 //
#define PI 3.1415926 // 用PI代表"3.14926"这个字符串.
    // a.用一个简单的名字,代替一个长字符串,所以这个标识符也称为宏名,在预编译时将宏名替换为字符串的过程叫做宏展开.
    // 增加了修改的方便性,为修改提供了极大的便利,提高了程序的可移植性
    // 1.宏名一般用大写字母
    // 2.宏定义,不是c语句,也不用行末加; 宏名的有效范围在#define之后,到本文件结束,不能夸文件使用
    // 3.可以用#undef终止宏定义的作用域
    // 4.#define进行宏定义时,还可以引用已经定义的宏
    // #define DPI 2*PI

    // 2.带参数的宏定义
    // #define 宏名(参数表) 字符串
    // 也是用右边的字符串,代替宏名(参数表)
    // #define S(a,b) a*b
    // S(2,3) -->替换为6

    // 1.作为输入
    // #define S(r) PI * r * r
    // S(1+5)-->会替换为 PI * 1+5 * 1+5 -->这样结果就错了,所以会#define S(r) PI * (r) *(r)
    // 2.宏定义时宏名和带参数的括号之间不能加空格,否则,空格以后的字符都作为替换字符串的一部分了.
    // #define S (4)// err
    //

    // 条件编译
    // 形式1 : 当标志符没有被定义过#define 则对该程序段进行编译,
    // #ifndef 标志符
    //  程序段
    // #else
    //  程序段
    // #endif

    // 形式2 : 当标志符没有被定义过#define 则对该程序段进行编译,
    // #ifndef 标志符
    //  程序段
    // #else
    //  程序段
    // #endif

    // 形式3 : 当指定表达式的值为真,就编译程序段,
    // #if 表达式
    //  程序段
    // #else
    //  程序段
    // #ednif

    // #if 表达式
    //  程序段
    // #elif
    //  程序段
    // #ednif
#endif
#if 0
    // &* *& --> 优先级同级,从右到左结合的
    // 函数指针变量调用函数
    // 一个函数在编译的时候,系统会给这个函数分配一个入口地址,这个入口地址就称为函数从的指针.
    // 即然这个函数有地址,就可以用指针来指向它.
    int max(int x, int y)
    {
        static int c = 0;
        c++;
        printf("max[%d]\n",c);
        return x>y?x:y;
    }
    int (*p)(int x,int y);
    p = max(3,4);// 现在用p来调用max
    int ccc = 0;
    //p = &max;//将函数的max的入口地址赋给指针变量p,函数名代表函数的入口地址,
    p = max;
    ccc = (*p)(5,19);// 这里*p取代了max,
    printf("[%d]\n",ccc);
    ccc = p(25,19);// 这里*p取代了max,
    printf("[%d]\n",ccc);

#endif
#if 0
    // 我们认为应用程序生成以后,在逻辑上它独立占有4GB地址空间(32),所以应用程序的地址就是从4g空间编码.
    // 当你的程序被利器拉起来就进入了内存空间,如果有操作系统调度的话,那么这个东西就称为进程,你的空间就是进程空间.
    // 局部变量在栈区,靠近上不0xC000000,栈地址从高到低生长的
    // 全局和静态变量靠近0x0804800方向的,因为他们在存储中都在可读写区域

    // 符号定义:main函数,buf变量
    // 符号引用 : extern,外部引用,本文件引用
    // 局部变量分配在栈中,不会再过程外被引用,不是符号定义
    // 对于c程序员,来说,透掌握c代码在内存中的具体的分布是核心能力.
    // 核心是程序和被操作系统拉起来的.
#endif
#if 1
    // 学习时,有趣味,为什么这样做,原理又是什么?
    // qt,迅速上手,避免枯燥的语法学习
    float x;
    int i;
    x = 3.6;
    i = (int)x;
    printf("i=[%f]\n",i);
    printf("===============\n");
    printf("i=[%f],x=[%d],i=[%f],x=[%d]\n",i,x,i,x);
#endif
#if 0// C语言中的printf用%d输出float类型数据出现或以%f输出int型数据的结果为什么是0
    **************float类型%d输出*************************
            float a=7.5f;
    如果用printf(“%d”,a);输出的是0。
            但float型用%d输出是否一定是0呢，答案肯定不都是0；
            为什么 7.5 用%d输出的是0？分析如下：
            首先来了解下printf的输出格式，int 和 long int 都是32位的，用%d输出；float 、double都是%f输出，但 float 是32位的，double 是64位的，所以在参数传递的时候C语言统一将 float 类型数值传换为 double 类型再传入 printf 函数。如果是32位整型则输出格式为%lld。
            下面来讲一下  float a=7.5f ; printf(“%d”,a)输出为0的情况：
            %d只输出低32位的数据，并将这些32位二进制以十进制数输出，编译器首先将 7.5从float类型转换为double类型，7.5在内存中的存放方式是0x40f00000，转换成double类型在内存中的数据就是这个0x401e000000000000，这个内存数据可以很明显看出低32位全是0，而%d则只能截取到低32位，所以这个以%d输出7.5的数值当然是 0了。如大家不相信可以用%lld 输出看看，这个%lld就很读到低64位数据，读出的结果就是0x401e000000000000，在屏幕上看到一个很大的十进制数。
            如果我一定要输出7.5在内存中的存放方法怎么办呢？
            可以用printf(“%d”,*(int *)&a);这里做了一下处理，不是直接把a传进来，把a所在地址里的内容处理了一下，不管a是什么类型，只对地址进行操作，利用（int *）&a，将a所在地址中的内容0x40f00000直接当成 int 类型传给printf，int 的类型数据不会再转成double类型了，所以输出正常，这个只是针对浮点型数据只占低32位，如果输出64位还得用%lld格式控制输出。
            如果用printf(“%d”,(int)a),输出行不行，这个强制类型转换只针对a的数据类型进行转换，7.5转换 int 类型是7，而上面的*(int *)&a，是对内存中的实际存储数据进行操作，蔽开数据类型这一层面，只将这个数据0x40f00000直接转成int类型输出。而（int）a，要先看a的类型，C语言会根据所要数据类型，对内存存储的数据进行改变，以便可以用int类型正确解析内存数据。
            如果用printf(“%d”,(float)a)，输出什么，输出的是0，这个只是将a的float类型还转成float类型，还是自动转成doube类型，传给printf函数。
            为什么float非要转成double类型呢，因为printf格式控制浮点型输出只有%f，所以统一按doube类型输出，不像整型有32位的%d或%ld，64位的有%lld，这就将32位整型和64位整型用不同的格式控制分开了，而%f则没有，所以printf输出的浮点数其实是统一遍历了64位内存，如果float传入printf没有进行转换，那么printf输出高32位数据将不可预知，printf输出结果也就不正确了，因此传入printf的浮点数都会被编译器隐含转成double类型。
            ***********************int类型%f格式输出************************************
            如果定义了int a=0x40f00000;用printf(“%f”,a)输出的结果是多少呢？
            答案是0，至少我们看的屏幕上显示的是0.000000，实际值可不是0啊，只是我们显示的精度只能有15位小数，而实际的数据可能很小很小，0.0000….000几百个0后会有几个有效数据，我们分析一下。
            首先C语言把a传进printf，因为a是整型，所以不会自动转成double型数据，直接将0x40f00000传进printf，而%f寻的是64位内存，也就是把0x0000000040f00000这个内存中的数据当成浮点型输出来，那浮点型的数据是多少呢，又是怎么存储的呢？
            64位浮点数的存放方式：
            63位                 62~52位                 51~0位
            1个符号位        11个阶数                 52个尾数
            从0x0000000040f00000来看
            1）符号位是0，表示正
            2）阶数是0，表示-1023 + 1023 = 0，用指数表示：1.#*2^-1023，‘#’是代表尾数。
            3）尾数就是，0x0000040f00000
            4）浮点二进制表示
            2#1.0000000000000000000001000000111100000000000000000000*2^(-1023)，-1023次方可想而知有多小。
            这就是为什么我们的int型数据用%f输出是0.000000的原因。
        #endif

            printf("Hello World!\n");
    return 0;
}
